package br.edu.unifei.cct720.trabalho06.model.preenchimento;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import br.edu.unifei.cct720.trabalho06.model.formas.Borda;
import br.edu.unifei.cct720.trabalho06.model.formas.Poligono;
import br.edu.unifei.cct720.trabalho06.model.formas.Reta;
import br.edu.unifei.cct720.trabalho06.model.ponto.Ponto;

/**
 * Classe responsavel por tratar o preenchimento
 * do poligono atraves das scanlines
 * 
 * @author fknappe
 *
 */
public class Preenchimento {

	/**
	 * Metodo responsavel por criar o preenchimento do
	 * poligono
	 * 
	 * @param Poligono poligono
	 * @param int passo
	 * 
	 * @return ArrayList<Reta>
	 */
	public static ArrayList<Reta> preenchePoligo(Poligono poligono, int passo) {
		
		// Declaracao de variaveis locais
		Scanlines scanlines = null;
		
		// Montando a lista de scanlines do poligono
		scanlines = montaListaDeScanLines(poligono);
		
		// Ajustando as scanlines de acordo com as bordas
		ajustaScanLines(scanlines);
		
		// Calculando o novo posicionamento das scanlines
		calculaScanlines(scanlines);
		
		// Ordenando a lista das scanlines
		ordenaScanlines(scanlines);
		
		// Construindo o preenchimento
		return constroiPreenchimento(scanlines, poligono, passo);
	}

	/**
	 * Metodo responsavel por montar as retas
	 * do preenchimento
	 * 
	 * @param Poligono poligono
	 * 
	 * @return
	 */
	private static Scanlines montaListaDeScanLines(Poligono poligono) {
		
		// Declaracao de variaveis locais
		ArrayList<Reta> arestas = null;
		List<Reta> arestasBackup = null;
		BoundingBox boundingBox = null;
		Scanlines scanlines = null;
		Reta aresta = null;
		Ponto p1, p2 = null;
		int retorno = 0;
		
		arestas = poligono.getRetas();
		arestasBackup = (ArrayList<Reta>) arestas.clone();
		boundingBox = poligono.getBoundingBox();
		scanlines = new Scanlines(boundingBox.getYMax() + 1);
		
		for (int i = boundingBox.getYMin(); i <= boundingBox.getYMax(); i++) {
			for (int j = boundingBox.getXMin(); j <= boundingBox.getXMax(); j++) {
				for (int k = arestas.size() - 1; k >= 0; k--) {
					aresta = arestas.get(k);
					p1 = aresta.getInicio();
					p2 = aresta.getFim();

					if (p1.getY() == i || p2.getY() == i) {
						if (p1.getX() == j || p2.getX() == j) {
							int xMin;
							int yMin;
							
							if (p1.getY() < p2.getY()) {
								xMin = p1.getX();
								yMin = p1.getY();
							} else {
								xMin = p2.getX();
								yMin = p2.getY();
							}

							double inclinacao = 1 / aresta.getM();
							if (!Double.isInfinite(inclinacao)) {					
								Borda borda = new Borda(Math.max(p1.getY(), p2.getY()), xMin, inclinacao);
								if (!scanlines.contains(borda, i)) {
									for (Reta retaAux : arestasBackup) {
										retorno = retaAux.temPonto(new Ponto(xMin, yMin, null));

										if (retorno == 1) {
											if (retaAux.getFim().getY() <= yMin) {
												borda.setTroca(true);
											}
										}
										if (retorno == 2) {
											if (retaAux.getInicio().getY() <= yMin) {
												borda.setTroca(true);
											}
										}
									}
									scanlines.addBorda(borda, i);
									arestas.remove(aresta);
								}
							}
						}
					}
				}
			}
		}
		return scanlines;
	}

	/**
	 * Metodo responsavel por ajustar as bordas entre
	 * as scanlines
	 * 
	 * @param Scanlines scanlines
	 * 
	 * @return void
	 */
	private static void ajustaScanLines(Scanlines scanlines) {
		
		// Declaracao das variaveis locais
		Scanline scanline = null;
		Borda borda = null;
		
		for (int i = 0; i < scanlines.size() - 1; i++) {
			scanline = scanlines.get(i);
			for (int j = 0; j < scanline.getBordas().size(); j++) {
				borda = scanline.getBordas().get(j);
				if (borda.isTroca()) {
					borda.trocaScanline(1); 
					scanline.getBordas().remove(borda);
					scanlines.get(i + 1).addBorda(borda);
				}
			}
		}
	}

	/**
	 * Metodo responsavel por calcular os pontos criticos
	 * das bordas das scanlines
	 * 
	 * @param Scanlines scanlines
	 * 
	 * @return void
	 */
	private static void calculaScanlines(Scanlines scanlines) {
		
		// Declaracao de variaveis locais
		Scanline scanline = null;
		Borda borda = null;
		
		// Percorrendo a lista de scanlines
		for (int i = 0; i < scanlines.size(); i++) {
			scanline = scanlines.get(i);
			for (int j = 0; j < scanline.getBordas().size(); j++) {
				borda = scanline.getBordas().get(j).clone();
				// Verificando posicionamento das scanlines
				if (borda.getY() > i) {
					borda.trocaScanline(1);
					scanlines.get(i + 1).addBorda(borda);
				}
			}
		}
	}

	/**
	 * Metodo responsavel por ordenar as scanlines
	 * na lista de scanlines, de acordo com a borda
	 * 
	 * @param Scanlines scanlines
	 * 
	 * @return void
	 */
	private static void ordenaScanlines(Scanlines scanlines) {
		for (int i = 0; i < scanlines.size(); i++) {
			scanlines.get(i).sort();
		}
	}

	/**
	 * Metodo responsavel por realizar o calculo do preenchimento 
	 * do poligono
	 * 
	 * @param Scanlines scanlines
	 * @param int passo
	 * 
	 * @return ArrayList<Reta>
	 */
	private static ArrayList<Reta> constroiPreenchimento(Scanlines scanlines, Poligono poligono, int passo) {
		
		// Declaracao das variaveis locais
		ArrayList<Reta> preenchimento = null;
		Scanline scanline = null;
		Ponto inicio, fim = null;
		
		// Incializando a lista de retas do preenchimento
		preenchimento = new ArrayList<Reta>();
		
		// Construcao das retas do preenchimento
		for (int i = 0; i < scanlines.size(); i = i + passo) {
			scanline = scanlines.get(i);
			while (scanline.getBordas().size() > 1) {
				inicio = new Ponto((int) scanline.getBordas().remove(0).getX(), i, null);
				fim = new Ponto((int) scanline.getBordas().remove(0).getX(), i, null);
				
				// Adiciona a nova scanline a lista
				preenchimento.add(new Reta(inicio, fim, Color.BLUE));
			}
		}
		return preenchimento;
	}
}
